//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using OpenLS.Core.Notification;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Parsing;

namespace OpenLS.Spreadsheet
{    /// <summary>
    /// Represents the data validation of a worksheet range
    /// </summary>
    public class DataValidation : ViewModelBase, IOfficeXmlSerializable
    {
#if DEBUG
    public DataValidation()
    {
        
    }
#endif
        private Worksheet _worksheet;
        internal Worksheet Worksheet
        {
            get { return _worksheet; }
            set
            {
                _worksheet = value;
                if (value != null)
                {
                    if (_formula1 is UnparsedFormula)
                        _formula1 = CellFormulaBase.Parse(_formula1.Text, _worksheet);
                    if (_formula2 is UnparsedFormula)
                        _formula2 = CellFormulaBase.Parse(_formula2.Text, _worksheet);
                }
            }
        }

        private bool _allowBlank;
        private bool _dropDownVisible;
        private string _error;
        private DataValidationErrorStyle _errorStyle;
        private string _errorTitle;
    internal CellFormulaBase _formula1;

        /// <summary>
        /// Get or set the second formula
        /// </summary>
        internal CellFormulaBase _formula2;

        private DataValidationImeMode _imeMode;
        private bool _showErrorMessage;
        private bool _showInputMessage;

        private DataValidationType _type;
        private DataValidationOperator _op;
        private string _prompt;
        private string _promptTitle;

        /// <summary>
        /// Get or set the first formula
        /// </summary>
        public string Formula1
        {
            get
            {
                return _formula1 == null ? null : _formula1.TextWithoutEqual;
            }
            set
            {
                _formula1 = CellFormulaBase.Parse(value, this.Worksheet); //\\ prepend "="?
                notifyPropertyChanged("Formula1");
            }
        }

        public string Formula2
        {
            get { return _formula2 == null ? null : _formula2.Text; }
            set
            {
                _formula2 = CellFormulaBase.Parse(value, null); //\\ prepend "="?
                notifyPropertyChanged("Formula2");
            }
        }

        public DataValidationType Type
        {
            get { return _type; }
            set
            {
                _type = value;
                notifyPropertyChanged("Type");
            }
        }

        public DataValidationErrorStyle ErrorStyle
        {
            get { return _errorStyle; }
            set
            {
                _errorStyle = value;
                notifyPropertyChanged("ErrorStyle");
            }
        }

        public DataValidationImeMode ImeMode
        {
            get { return _imeMode; }
            set
            {
                _imeMode = value;
                notifyPropertyChanged("ImeMode");
            }
        }

        public DataValidationOperator Operator
        {
            get { return _op; }
            set
            {
                _op = value;
                notifyPropertyChanged("Operator");
            }
        }

        public bool AllowBlank
        {
            get { return _allowBlank; }
            set
            {
                _allowBlank = value;
                notifyPropertyChanged("AllowBlank");
            }
        }

        public bool DropDownVisible
        {
            get { return _dropDownVisible; }
            set
            {
                _dropDownVisible = value;
                notifyPropertyChanged("DropDownVisible");
            }
        }

        public bool ShowInputMessage
        {
            get { return _showInputMessage; }
            set
            {
                _showInputMessage = value;
                notifyPropertyChanged("ShowInputMessage");
            }
        }

        public bool ShowErrorMessage
        {
            get { return _showErrorMessage; }
            set
            {
                _showErrorMessage = value;
                notifyPropertyChanged("ShowErrorMessage");
            }
        }

        public string ErrorTitle
        {
            get { return _errorTitle; }
            set
            {
                Contract.Requires(value == null || !value.Contains("\0"));
                _errorTitle = value;
                notifyPropertyChanged("ErrorTitle");
            }
        }

        public string Error
        {
            get { return _error; }
            set
            {
                Contract.Requires(value == null || !value.Contains("\0"));
                _error = value;
                notifyPropertyChanged("Error");
            }
        }

        public string PromptTitle
        {
            get { return _promptTitle; }
            set
            {
                _promptTitle = value;
                notifyPropertyChanged("PromptTitle");
            }
        }

        public string Prompt
        {
            get { return _prompt; }
            set
            {
                _prompt = value;
                notifyPropertyChanged("Prompt");
            }
        }


        ///<summary>
        /// Get or set the range where the data valibation should be applied.
        ///</summary>
        public Range Range
        {
            get { return Reference.Range; }
            set { Reference = value.ToReference(); }
        }
        internal bool AppliesTo(Cell cell)
        {
            Contract.Requires(cell != null);
            return Range.Contains(cell) && this.ShowErrorMessage;
        }

        private RangeReference _reference;
        internal RangeReference Reference
        {
            get { return _reference; }
            set { _reference = value;notifyPropertyChanged("Reference"); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IOfficeXmlSerializable Members

        void IOfficeXmlSerializable.ReadXml(ReadContext context)
        {
            using (ReadContext c = context.Read("dataValidation"))
            {
                Type = c.GetOptionalEnum("type", DataValidationType.None);
                ErrorStyle = c.GetOptionalEnum("errorStyle", DataValidationErrorStyle.Stop);
                ImeMode = c.GetOptionalEnum("imeMode", DataValidationImeMode.NoControl);
                Operator = c.GetOptionalEnum("operator", DataValidationOperator.Between);
                AllowBlank = c.GetOptionalBoolean("allowBlank", false);
                DropDownVisible = c.GetOptionalBoolean("showDropDown", false);
                ShowInputMessage = c.GetOptionalBoolean("showInputMessage", false);
                ShowErrorMessage = c.GetOptionalBoolean("showErrorMessage", false);
                ErrorTitle = c.GetOptionalString("errorTitle");
                Error = c.GetOptionalString("error");
                PromptTitle = c.GetOptionalString("promptTitle");
                Prompt = c.GetOptionalString("prompt");
                string sqref = c.GetOptionalString("sqref");
                if (sqref != null)
                {
                    var worksheet = c.GetData<Worksheet>();
                    Reference = worksheet.GetRangeReference(sqref);
                }
                else
                {
                    // Contract.Assert(false);
                }
                Formula1 = c.ReadOptionalElementString("formula1");
                Formula2 = c.ReadOptionalElementString("formula2");
            }
        }

        void IOfficeXmlSerializable.WriteXml(WriteContext context)
        {
            using (WriteContext c = context.Write("dataValidation"))
            {
                c.SetOptionalEnum("type", Type, DataValidationType.None);
                c.SetOptionalEnum("errorStyle", ErrorStyle, DataValidationErrorStyle.Stop);
                c.SetOptionalEnum("imeMode", ImeMode, DataValidationImeMode.NoControl);
                c.SetOptionalEnum("operator", Operator, DataValidationOperator.Between);
                c.SetOptionalBoolean("allowBlank", AllowBlank, false);
                c.SetOptionalBoolean("showDropDown", DropDownVisible, false);
                c.SetOptionalBoolean("showInputMessage", ShowInputMessage, false);
                c.SetOptionalBoolean("showErrorMessage", ShowErrorMessage, false);
                c.SetOptionalString("errorTitle", ErrorTitle);
                c.SetOptionalString("error", Error);
                c.SetOptionalString("promptTitle", PromptTitle);
                c.SetOptionalString("prompt", Prompt);
                if (Reference != null)
                    c.SetOptionalString("sqref", Reference.Label);
                c.WriteOptionalElementString("formula1", Formula1);
                c.WriteOptionalElementString("formula2", Formula2);
            }
        }

        #endregion

        internal bool? IsValidValue(object value, Cell cell)
        {
            Contract.Requires(cell != null);
            if (Type == DataValidationType.None)
                return true;
            if (value == null && AllowBlank)
                return true;
            switch (Type)
            {
                case DataValidationType.None:
                    return true;
                case DataValidationType.Decimal:
                    if (!CellHelper.IsDoubleValue(value))
                        return false;
                    return IsValidDecimalValue(CellHelper.DoubleValue(value));
                case DataValidationType.Whole:
                    {
                        if (!CellHelper.IsDoubleValue(value))
                            return false;
                        double dValue = CellHelper.DoubleValue(value);
                        if (Math.Round(dValue) != dValue)
                            return false;
                        return IsValidDecimalValue(dValue);
                    }
                case DataValidationType.TextLength:
                    if (value == null)
                        return false;
                    return IsValidDecimalValue((value.ToString().Length));

                case DataValidationType.List:
                    {
                        if (!CellHelper.IsDoubleValue(value))
                            return false;
                        double dValue = CellHelper.DoubleValue(value);
                        return Enumerable.Any(Enumerable.OfType<double>(GetValues(cell.Worksheet)), d => d == dValue);
                    }
                    break;
                case DataValidationType.Date:
                    {
                        if (value is DateTime)
                            value = cell.Worksheet.Workbook.DateTimeToDouble((DateTime) value);
                        return IsValidDecimalValue(value);
                    }
                case DataValidationType.Time:
                    {
                        if (value is DateTime)
                            value = cell.Worksheet.Workbook.DateTimeToDouble((DateTime)value);
                        return IsValidDecimalValue(value);
                    }
                case DataValidationType.Custom:
                    {
                        var f = _formula1.Offset(cell.Address - this.Range.TopLeftCell.Address);
                        var oldValue = cell.Value;                     
                        try
                        {
                            cell.setRawValue(value);
                            var v = f.GetValue();
                            if (v is bool? )
                            {
                                return (bool?) v;
                            }
                            return false;
                        }
                        finally
                        {
                            cell.setRawValue(oldValue);
                        }
                    }
                default: throw new NotSupportedException();

            }
        }

        private bool? IsValidDecimalValue(object value)
        {
            Contract.Requires(Type == DataValidationType.Decimal || this.Type == DataValidationType.Whole);
            var comparison1 = _formula1.CompareValueTo(value, this.Worksheet.Workbook);
            switch (this.Operator)
            {
                case DataValidationOperator.GreaterThan:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.GreaterThan:
                                return true;
                            case CellFormulaBase.ComparisonResult.Equal:
                            case CellFormulaBase.ComparisonResult.NotEqual:
                            case CellFormulaBase.ComparisonResult.LessThan:
                                return false;
                            case CellFormulaBase.ComparisonResult.NotComparable:
                                return null;
                            default: throw new NotSupportedException();
                        }
                    }

                case DataValidationOperator.GreaterThanOrEqual:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.GreaterThan:
                            case CellFormulaBase.ComparisonResult.Equal:
                                return true;
                            case CellFormulaBase.ComparisonResult.NotEqual:
                            case CellFormulaBase.ComparisonResult.LessThan:
                                return false;
                            case CellFormulaBase.ComparisonResult.NotComparable:
                                return null;
                            default: throw new NotSupportedException();
                        }
                    }
                case DataValidationOperator.LessThan:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.LessThan:
                                return true;
                            case CellFormulaBase.ComparisonResult.GreaterThan:
                            case CellFormulaBase.ComparisonResult.Equal:
                            case CellFormulaBase.ComparisonResult.NotEqual:
                                return false;
                            case CellFormulaBase.ComparisonResult.NotComparable:
                                return null;
                            default: throw new NotSupportedException();
                        }
                    }
                case DataValidationOperator.LessThanOrEqual:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.LessThan:
                            case CellFormulaBase.ComparisonResult.Equal:
                                return true;
                            case CellFormulaBase.ComparisonResult.GreaterThan:
                            case CellFormulaBase.ComparisonResult.NotEqual:
                                return false;
                            case CellFormulaBase.ComparisonResult.NotComparable:
                                return null;
                            default: throw new NotSupportedException();
                        }
                    }
                case DataValidationOperator.Equal:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.Equal:
                                return true;
                            case CellFormulaBase.ComparisonResult.NotEqual:
                            case CellFormulaBase.ComparisonResult.GreaterThan:
                            case CellFormulaBase.ComparisonResult.LessThan:
                                return false;
                            case CellFormulaBase.ComparisonResult.NotComparable:
                                return null;
                            default: throw new NotSupportedException();
                        }
                    }
                case DataValidationOperator.NotEqual:
                    {
                        switch (comparison1)
                        {
                            case CellFormulaBase.ComparisonResult.NotEqual:
                                return true;
                            case CellFormulaBase.ComparisonResult.Equal:
                                return false;
                            default:
                                return null;
                        }
                    }

                case DataValidationOperator.Between:
                    {
                        var comparison2 = _formula2.CompareValueTo(value, this.Worksheet.Workbook);
                        if (comparison1 == CellFormulaBase.ComparisonResult.LessThan || comparison1 == CellFormulaBase.ComparisonResult.Equal)
                            if (comparison2 == CellFormulaBase.ComparisonResult.GreaterThan || comparison2 == CellFormulaBase.ComparisonResult.Equal)
                                return true;
                        if (comparison1 == CellFormulaBase.ComparisonResult.NotComparable || comparison2 == CellFormulaBase.ComparisonResult.NotComparable)
                            return null;
                        return false;


                    }
                case DataValidationOperator.NotBetween:
                    {
                        var comparison2 = _formula2.CompareValueTo(value, this.Worksheet.Workbook);
                        if (comparison1 == CellFormulaBase.ComparisonResult.LessThan ||
                            comparison1 == CellFormulaBase.ComparisonResult.Equal)
                            if (comparison2 == CellFormulaBase.ComparisonResult.GreaterThan ||
                                comparison2 == CellFormulaBase.ComparisonResult.Equal)
                                return false;
                        if (comparison1 == CellFormulaBase.ComparisonResult.NotComparable ||
                            comparison2 == CellFormulaBase.ComparisonResult.NotComparable)
                            return null;
                        return true;
                    }
                default: throw new NotSupportedException();

            }
            return true;
        }

        internal IEnumerable<object> GetValues(Worksheet worksheet)
        {
            if (Formula1.StartsWith("\""))
            {
                string s = Formula1.Substring(1, Formula1.Length - 2);
                foreach (var ss in s.Split(','))
                {
                    double d;
                    if (double.TryParse(ss, out d))
                        yield return d;
                    else
                    {
                        yield return ss;
                    }
                }
                yield break;
            }
            RangeReference exp = worksheet.GetRangeReference(Formula1);
            foreach (Cell cell in exp.Range.GetNonEmptyCells())
                yield return cell.Value;
        }

    
        internal DataValidation Projected(Worksheet worksheet, CellRelativeAddress offset)
        {
            Contract.Requires(worksheet != null);
            Contract.Requires(this.Reference != null);
            Contract.Ensures(Contract.Result<DataValidation>() != null);
            Contract.Ensures(Contract.Result<DataValidation>().Reference != null);
            DataValidation result = (DataValidation)MemberwiseClone();
            result.Reference = worksheet.GetRange(Reference.Label).Offseted(offset).ToReference();
            Contract.Assert(result.Reference != null);
            result._worksheet = worksheet;
            if (_formula1 != null)
            {
                result.Formula1 = Formula1;
                if (_formula1 is WorksheetExpressionFormula)
                {
                    _formula1 = new WorksheetExpressionFormula(((ExpressionFormula)_formula1).Expression.Offset(offset),
                                                      worksheet);
                }
            }
            if (Formula2 != null)
            {
                result.Formula2 = Formula2;
                var f = _formula2;
                if (_formula2 is WorksheetExpressionFormula)
                {
                    _formula2 = new WorksheetExpressionFormula(((ExpressionFormula)_formula2).Expression.Offset(offset),
                                                      worksheet);
                }
            }
            return result;
        }
    }

}